/*******************************************************************************
 * The MOSES project, model based software engineering of networked embedded systems.
 * 
 * Copyright (C) 2010-2013, University of Applied Sciences Wildau,
 * Telematics Research Group, http://www.tm.th-wildau.de/research-group
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 3 of the
 * License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses>.
 * 
 * Contributors:
 *     University of Applied Sciences Wildau - initial API and implementation
 * 
 * If you are interested in a commercial license please contact us via 
 * e-mail: ralf.vandenhouten@th-wildau.de.
 ******************************************************************************/
package de.thwildau.tm.moses.asn1.util;

import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.xtext.naming.QualifiedName;
import org.eclipse.xtext.xbase.lib.StringExtensions;

import com.google.inject.Inject;

import de.thwildau.tm.moses.asn1.asn1.Assignment;
import de.thwildau.tm.moses.asn1.asn1.ModuleDefinition;
import de.thwildau.tm.moses.asn1.lang.SizeConstraint;

/**
 * Name Converter for Java as the target language.
 * 
 * @author Thomas Kistel
 * @version $Revision: 763 $
 */
public class JavaNamesConverter implements NamesConverter {
    
    private enum JavaArtifact {
        PACKAGE_NAME,
        CLASS_NAME,
        FIELD_NAME
    }

    private static final int PACKAGE_START_INDEX = 3;

    @Inject
    private Asn1ElementFinder finder;
    
    @SizeConstraint(max=2)
    private String test;

    /* (non-Javadoc)
     * @see de.thwildau.tm.moses.asn1.util.NamesConverter#getPackageName(org.eclipse.emf.ecore.resource.Resource)
     */
    @Override
    public QualifiedName getPackageName(Resource resource) {
        URI uri = resource.getURI();
        if (uri.isPlatformResource()) {
            int segments = uri.segmentCount();
            if (segments > PACKAGE_START_INDEX) {
                StringBuilder buf = new StringBuilder();
                for (int i = PACKAGE_START_INDEX; i < segments - 1; i++) {
                    if (i > PACKAGE_START_INDEX)
                        buf.append(".");
                    buf.append(uri.segment(i));
                }
                QualifiedName qn = QualifiedName.create(buf.toString());
                return qn;
            }
        }
        return null;
    }

    /* (non-Javadoc)
     * @see de.thwildau.tm.moses.asn1.util.NamesConverter#getResourceDescription(T)
     */
    @Override
    public <T extends Assignment> EObjectResourceDescription<T> getResourceDescription(T element) {
        ModuleDefinition moduleDefinition = (ModuleDefinition)element.eContainer().eContainer();
        QualifiedName fqn = toPackageName(moduleDefinition);
        QualifiedName className = normalizeAsn1Reference(element.getName(), JavaArtifact.CLASS_NAME);
        fqn = fqn.append(className);
        return new EObjectResourceDescriptionImpl<T>(element, fqn);
    }
    
    /*
     * (non-Javadoc)
     * @see NamesConverter#getResourceDescription(org.eclipse.emf.ecore.EObject, java.lang.String)
     */
    public <T extends EObject> EObjectResourceDescription<T> getResourceDescription(T element, String name) {
        ModuleDefinition moduleDefinition = finder.getModuleDefinitionOf(element);
        if(moduleDefinition == null) {
            throw new IllegalArgumentException("The given element " + element.eClass().getName() + " is not part of a ModuleDefinition.");
        }
        QualifiedName fqn = toPackageName(moduleDefinition);
        QualifiedName className = normalizeAsn1Reference(name, JavaArtifact.CLASS_NAME);
        fqn = fqn.append(className);
        return new EObjectResourceDescriptionImpl<T>(element, fqn);
    }
    

    /* (non-Javadoc)
     * @see de.thwildau.tm.moses.asn1.util.NamesConverter#getModuleReferenceAsPackage(de.thwildau.tm.moses.asn1.asn1.ModuleDefinition)
     */
    @Override
    public QualifiedName toPackageName(ModuleDefinition moduleDefinition) {
        String reference = moduleDefinition.getModuleIdentifier().getModuleReference();
        return normalizeAsn1Reference(reference, JavaArtifact.PACKAGE_NAME);
    }
    
    /*
     * (non-Javadoc)
     * @see de.thwildau.tm.moses.asn1.util.NamesConverter#toClassName(java.lang.String)
     */
    @Override
    public QualifiedName toClassName(String reference) {
        return normalizeAsn1Reference(reference, JavaArtifact.CLASS_NAME);
    }
    
    /* (non-Javadoc)
     * @see de.thwildau.tm.moses.asn1.util.NamesConverter#convertToFieldName(java.lang.String)
     */
    @Override
    public String toFieldName(String reference) {
        return normalizeAsn1Reference(reference, JavaArtifact.FIELD_NAME).toString();
    }
    
    /* (non-Javadoc)
     * @see de.thwildau.tm.moses.asn1.util.NamesConverter#convertToEnumField(java.lang.String)
     */
    @Override
    public String toEnumField(String reference) {
        reference = reference.replaceAll("-", "_");
        return reference.toUpperCase();
    }

    private QualifiedName normalizeAsn1Reference(String reference, JavaArtifact javaArtifact) {
        String[] referenceTokens = reference.split("-");
        for (int i = 0; i < referenceTokens.length; i++) {
            String token = referenceTokens[i];
            switch (javaArtifact) {
                case PACKAGE_NAME:
                    referenceTokens[i] = processPackageNameToken(token);
                    break;
                case CLASS_NAME:
                    referenceTokens[i] = processClassNameToken(i, token);
                    break;
                case FIELD_NAME:
                    referenceTokens[i] = processAttributeToken(referenceTokens.length, i, token);
                    break;
                default:
                    throw new IllegalArgumentException();
            }
        }

        switch (javaArtifact) {
            case PACKAGE_NAME:
                return QualifiedName.create(referenceTokens);
            case CLASS_NAME:
            case FIELD_NAME:
                return QualifiedName.create(concat(referenceTokens));
            default:
                throw new IllegalArgumentException();
        }
    }
    
    private String processPackageNameToken(String token) {
        if (Character.isDigit(token.charAt(0))) {
            throw new IllegalArgumentException(
                    "A Java package fragment (" + token + ") cannot start with a digit. The ASN.1 reference should not contain a digit after a hyphen.");
        }
        return token.toLowerCase();
    }
    
    private String processClassNameToken(int index, String token) {
        if(index == 0 && Character.isDigit(token.charAt(0))) {
            throw new IllegalArgumentException(
                    "A Java class name (" + token + ") cannot start with a digit. The ASN.1 reference should not start with a digit.");
        }
        return String.valueOf(token.charAt(0)).toUpperCase() + token.substring(1);
    }
    
    private String processAttributeToken(int totalLength, int index, String token) {
        if(index == 0 && Character.isDigit(token.charAt(0))) {
            throw new IllegalArgumentException(
                    "A Java attribute name (" + token + ") cannot start with a digit. The ASN.1 reference should not start with a digit.");
        }
        
        if(totalLength > 1) {
            token = token.toLowerCase();
            if(index > 0)
                token = StringExtensions.toFirstUpper(token);
        } else {
            // if we only have one Token in total, then just lower the first character
            token = StringExtensions.toFirstLower(token);
        }
         
        return token; 
    }

    /**
     * Concats the given string array to a single string.
     */
    private String concat(String[] array) {
        StringBuilder buf = new StringBuilder();
        for (String s : array)
            buf.append(s);
        return buf.toString();
    }
}
